#ifndef HOMEWORK1_TEST_H_INCLUDED
#define HOMEWORK1_TEST_H_INCLUDED

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/time.h>
#include <list>
#define SLEEP(ms) usleep( (ms) * 1000 )
#include "GlobalStuff.h"

static const char* fileName = "./Test.txt";
static const unsigned loops = 20;
static const unsigned position = 5;
int nextId;
BufferManager* mySut;


void *concTest(void* arg);

template<class T>
class Homework1_Test
{
public:
    void fullTest();
    void readTest();
    void writeTest();
    void writeTest2();
    void concurentTest();
    void createTestFile(unsigned multiplier);
protected:

private:
    static const unsigned TESTSIZE = 100;
    static const unsigned BUFFERSIZE = 5;
    static const unsigned NUM_THREADS = 10;

    FILE* testFile;


};

template<class T> void Homework1_Test<T>::fullTest()
{
    this->createTestFile(10);
    this->readTest();

    this->createTestFile(10);
    this->writeTest();

    this->createTestFile(10);
    this->writeTest2();

    this->createTestFile(10);
    this->concurentTest();
};

template<class T> void Homework1_Test<T>::readTest()
{
    timeval start, end;
    gettimeofday(&start, 0);
    cout << "starting readTest" << endl;
    T sut(fileName, BUFFERSIZE);

    for (unsigned i = 0; i < TESTSIZE; i++)
    {
        unsigned j = rand() % 10;
        BufferFrame& frame = sut.getPage(j, false);
        char* data = (char*) frame.getData();

        for (unsigned n = 0; n < pageSize; n++)
        {
            assert(data[n] == (char)j);
        }
        sut.unfixPage(frame, true);
    }
    gettimeofday(&end, 0);
    cout << "finished readTest: " << (end.tv_sec - start.tv_sec) << " sec" <<  endl;
}

template<class T> void Homework1_Test<T>::writeTest()
{
    timeval start, end;
    gettimeofday(&start, 0);
    cout << "starting writeTest" << endl;
    T sut(fileName, BUFFERSIZE);

    for (unsigned i = 0; i < TESTSIZE; i++)
    {
        unsigned j = rand() % 10;
        BufferFrame& frame = sut.getPage(j, false);
        char* data = (char*) frame.getData();

        for (unsigned n = 0; n < pageSize; n++)
        {
            data[n] = 'X';
        }

        for (unsigned n = 0; n < pageSize; n++)
        {
            assert(data[n] == 'X');
        }
        sut.unfixPage(frame, true);

        frame = sut.getPage(j, false);
        data = (char*) frame.getData();

        for (unsigned n = 0; n < pageSize; n++)
        {
            assert(data[n] == 'X');
        }
        sut.unfixPage(frame, false);
    }
    gettimeofday(&end, 0);
    cout << "finished writeTest: " << (end.tv_sec - start.tv_sec) << " sec" << endl;
}


template<class T> void Homework1_Test<T>::writeTest2()
{
    timeval start, end;
    gettimeofday(&start, 0);
    cout << "starting writeTest2" << endl;
    T sut(fileName, BUFFERSIZE);

    for (unsigned i = 0; i < getFileSize(); i++)
    {
        unsigned j = i;
        BufferFrame& frame = sut.getPage(j, false);
        char* data = (char*) frame.getData();

        for (unsigned n = 0; n < pageSize; n++)
        {
            data[n] = 'X';
        }

        for (unsigned n = 0; n < pageSize; n++)
        {
            assert(data[n] == 'X');
        }
        sut.unfixPage(frame, true);
    }

    for (unsigned i = 0; i < getFileSize(); i++)
    {
        unsigned j = i;
        BufferFrame& frame = sut.getPage(j, false);
        char* data = (char*) frame.getData();

        for (unsigned n = 0; n < pageSize; n++)
        {
            assert(data[n] == 'X');
        }
        sut.unfixPage(frame, false);
    }
    gettimeofday(&end, 0);
    cout << "finished writeTest2: " << (end.tv_sec - start.tv_sec) << " sec" << endl;
}

template<class T> void Homework1_Test<T>::concurentTest()
{
    timeval start, end;
    gettimeofday(&start, 0);
    cout << "starting concurentTest" << endl;
    mySut = new T(fileName, BUFFERSIZE);
    nextId = 0;
    pthread_t thread[NUM_THREADS];

    for (unsigned i = 0; i < NUM_THREADS; i++)
    {
        pthread_create (&thread[i], NULL, concTest, NULL);
    }

    for (unsigned i = 0; i < NUM_THREADS; i++)
    {
        pthread_join(thread[i], NULL);
    }
    gettimeofday(&end, 0);
    delete mySut;
    cout << "finished concreuntTest: " << (end.tv_sec - start.tv_sec) << " sec"  << endl;
}

template<class T> void Homework1_Test<T>::createTestFile(unsigned multiplier)
{
    //Create TestFile
    testFile = fopen(fileName, "w");
    char* data = new char[multiplier * pageSize];

    for (unsigned i = 0; i < multiplier; i++)
    {
        for (unsigned j = 0; j < pageSize; j++)
        {
            data[i * pageSize + j] = (char)i;
        }
    }

    fwrite((void*)data, sizeof(char), multiplier * pageSize, testFile);
    fclose(testFile);
}

void *concTest(void* arg)
{
    BufferManager& sut = *mySut;
    unsigned myId = nextId++;

    for (unsigned i = 0; i < loops; i++)
    {
        unsigned r = rand() % 2;
        BufferFrame& frame = sut.getPage(r, true);
        char* data = (char*)frame.getData();

        assert(data[position] == (char)r);
        data[position] = (char)myId;
        assert(data[position] == (char)myId);
        SLEEP(rand() % 300);
        assert(data[position] == (char)myId);
        data[position] = (char)r;
        SLEEP(rand() % 300);
        assert(data[position] == (char)r);

        sut.unfixPage(frame, false);
    }
    return 0;
}

#endif // HOMEWORK1_TEST_H_INCLUDED
